How to change or extend JavaScript in the Atomia User Panel.
Overview
The JavaScript used in the Atomia User Panel is built on top of the knockout.js library. If you are not familiar with it already you can look at the documentation and tutorials it provides. jQuery is also included and used for some DOM manipulation.
Knockout.js is a mature and stable JavaScript two-way-binding library with excellent documentation. In addition to the Atomia User Panel it is also used in Atomia Order Website, and in other large apps like the Microsoft Azure control panel.
Scope, Loading Order and Extension Points
All JavaScript is loaded at the bottom of the body tag. The scripts are loaded for and affect a different number of pages. These are the “scopes” available:
- App level: Scripts affecting every page of the Atomia User Panel. Loaded both from external script files and as inline scripts.
- Section level: Scripts affecting a collection of related pages, e.g. email section or databases section in the Atomia User Panel. Loaded from external script files.
- Page level: Initialization code for the current page. Loaded as inline scripts.
This is the loading order of the included JavaScript and extension points:
- Site.Master
- vendor.js
- Shared/_CommonResources.ascx
- Shared/_CommonScripts.ascx (including shared.js)
- Shared/_CustomScripts.ascx
- <section>/_Resources.ascx
- main.js
- <section>/_<ViewName>Scripts.ascx
- <section>/_<ViewName>ScriptsCustom.ascx
- script tag entry point
vendor.js (app level file)
Third party libraries and plugins, the main of which are knockout.js, jQuery, jquery-datatables, and (parts of) jQuery UI.
Shared/_CommonResources.ascx (app level script tag)
Initialization and extension of the Atomia.RESX and Atomia.URLS objects with shared localized strings available in JavaScript, and shared URL references.
Shared/_CommonScripts.ascx (app level script tag)
Global plugin settings and initialization of app wide knockout view models on the top level view model Atomia.VM. Do not override unless fully replacing app level scripts.
shared.js (app level file)
Pre-compiled JavaScript modules from Atomia that are shared on the app level, such as knockout bindings, shared mixins, utilities, and view models that are used in many places (like carts and payments).
Shared/_CustomScripts.ascx (app level)
Theme extension point for loading or initializing scripts that should be available in the whole app. Placed after the default scripts so they can be changed and extended.
<section>/_Resources.ascx (section level script tag)
Localized resource strings from mostly section’s App_LocalResources and locally useful URLs that should be available to JavaScript modules on Atomia.RESX and Atomia.URLS.
main.js (section level file)
Pre-compiled JavaScript modules from Atomia that are shared on the section level. Mostly constructors for view models that should be initialized and added to the Atomia.VM for individual pages. This is compiled on the section level instead of the page level to increase benefits of browser caching scripts.
<section>/_<ViewName>Scripts.ascx (page level script tag)
Mostly initialization and attachment to Atomia.VM of knockout view models that are used by the page. Do not override unless fully replacing scripts on a page.
<section>/_<ViewName>ScriptsCustom.ascx (page level)
Theme extension point for loading or initializing scripts that should be available on the page level. Placed after the default scripts so they can be changed and extended.
knockout entry point (app level script tag)
Call to ko.applyBindings on the Atomia.VM view model to initialize the knockout bindings on the page.
Available JavaScript Libraries and Modules
There are a number of JavaScript libraries and modules that are loaded and available to use in the Atomia User Panel:
Third party libraries
Knockout is available globally as ko, and related plugins like Knockout Postbox are located under this namespace as well.
jQuery and related plugins are available under the standard $ and jQuery.
Atomia modules
The Atomia namespace contains all modules and resources provided by Atomia.
Atomia.Shared
Contains view model constructors that are used in every page or many different sections of the Atomia User Panel, like AutopickerViewModel and SessionManagerViewModel. It also contains utilities like notify (used to trigger notifications) and renderKoTemplate (used to render a knockout template as a string).
Atomia.RESX
Makes available select localized resource strings from the *.resx files in App_GlobalResources and App_LocalResources. They are accessed by the same name as they have in the resource file, except they start with a lower-case letter to follow JavaScript conventions. E.g. a string with the name Confirm in the *.resx file will be available as Atomia.RESX.confirm.
Atomia.URLS
Makes available a selection of URLs that are used by the current page. They are named after the controller and action in case of internal links. So the action Edit on the WebsitesController will be available as Atomia.URLS.websitesEdit. Some URLs also have some value that should be replace for the URL to be usable. These are always delimited by the underscore character, so e.g. for the Atomia.URLS.websitesEdit URL to be usable you must replace the _adSearchQuery_ substring with the id of the website you would like to edit.
Atomia.<Section>
This is where most of the Atomia view model constructors are located. This namespace depends on what section of the Atomia User Panel the page is in. So e.g. the WebsitesListViewModel is located under the Atomia.Websites namespace. Only the relevant section namespace is loaded for each page.
How JavaScript Is Initialized on the Page
All relevant common and specific JavaScript view models that are need for a page in the Atomia User Panel are initialized and added to the root Atomia.VM view model. This view model is an empty object that serves only to have sub models attached to it, and is the view model that the knockout applyBindings call is made with. A typical view model structure on a page might look something like this:
- Atomia.VM (root view model)
- sessionManager (app level, SessionManagerViewModel instance)
- loading (app level, LoadingAnimationViewModel instance)
- menuModel (app level, MenuViewModel instance)
- sites (page level, websites index, WebsitesListViewModel instance)
- uncheckEmail (page level, websites index, UncheckMailSupportDialog instance)
- delWebsite (page level, websites index, DeleteWebsiteDialog instance)
- editWebsite (page level, websites index, EditWebsiteDialog instance)
Usually the view model hierarchy is quite flat like above, but a few complicated a page level models contain sub view models of their own.
App level view models are initialized and attached to Atomia.VM in the Shared/_CommonScripts.ascx partial view. The page level view models are initialized and attached to Atomia.VM in the <Section>/_<ViewName>Scripts.ascx partial views.
Generally, the different view models attached to Atomia.VM for a page do not know directly about each other. All communication between view models happens via a publish/subscribe system with the ko.postbox knockout plugin. This means that any view model can be swapped out for a custom one; it just has to listen to and handle the same events. Completely new and custom view models can also subscribe and do their own thing with events published by the existing view models on a page.